package com.lgl.scsapi.util.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.PoolException;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.concurrent.TimeUnit;

/**
 * 订单号生成工具
 */
public class BillNumberTool {
    private static Logger log = LoggerFactory.getLogger(BillNumberTool.class);
    private final long workerId;
    private final long snsEpoch = 1330328109047L;// 起始标记点，作为基准
    private long sequence = 0L;// 0，并发控制
    private final long workerIdBits = 10L;// 只允许workid的范围为：0-1023
    private final long maxWorkerId = -1L ^ -1L << this.workerIdBits;// 1023,1111111111,10位
    private final long sequenceBits = 12L;// sequence值控制在0-4095

    private final long workerIdShift = this.sequenceBits;// 12
    private final long timestampLeftShift = this.sequenceBits + this.workerIdBits;// 22
    private final long sequenceMask = -1L ^ -1L << this.sequenceBits;// 4095,111111111111,12位

    private long lastTimestamp = -1L;

    public BillNumberTool(long workerId) {
        super();
        if (workerId > this.maxWorkerId || workerId < 0) {// workid < 1024[10位：2的10次方]
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", this.maxWorkerId));
        }
        this.workerId = workerId;
    }

    public synchronized String nextBillNumber() throws Exception {
        long timestamp = this.timeGen();
        // 如果上一个timestamp与新产生的相等，则sequence加一(0-4095循环)，下次再使用时sequence是新值
        if (this.lastTimestamp == timestamp) {
            this.sequence = this.sequence + 1 & this.sequenceMask;
            if (this.sequence == 0) {
                timestamp = this.tilNextMillis(this.lastTimestamp);// 重新生成timestamp
            }
        } else {
            this.sequence = 0;
        }
        if (timestamp < this.lastTimestamp) {
            log.error(String.format("Clock moved backwards.Refusing to generate id for %d milliseconds", (this.lastTimestamp - timestamp)));
            throw new Exception(String.format("Clock moved backwards.Refusing to generate id for %d milliseconds", (this.lastTimestamp - timestamp)));
        }

        this.lastTimestamp = timestamp;
        // 生成的timestamp
        return (timestamp - this.snsEpoch << this.timestampLeftShift | this.workerId << this.workerIdShift | this.sequence) + "";
    }

    /**
     * 保证返回的毫秒数在参数之后
     * @param lastTimestamp
     * @return
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    /**
     * 获得系统当前毫秒数
     * @return
     */
    private long timeGen() {
        return System.currentTimeMillis();
    }

    /**
     * 获取单号(不包括客户端码，即单号的第1位)
     * @param billNoPrefix 单号前缀(1位客户端来源(0，1：PC，2，3，4：微信，5，6，7：安卓，8，9：IOS))
     * @param stringRedisTemplate redis对象
     * @return
     */
    public static String getBillNo(String billNoPrefix, StringRedisTemplate stringRedisTemplate) {
        //获取单号中的日期部分，如：191216
        String noDate = DateUtil.getNowDate("");
        noDate = noDate.substring(2);
        long billNum = 0;
        try {
            if (Constants.REDIS_IS_USABLE) {
                //从redis中获取单号的流水
                billNum = stringRedisTemplate.opsForValue().increment(Constants.BILL_NO_DATE + billNoPrefix + noDate);
                //如果是某天的第一次获取值，则给值添加过期时间，24小时
                if (billNum == 1) {
                    stringRedisTemplate.expire(Constants.BILL_NO_DATE + noDate, 24, TimeUnit.HOURS);
                }
            } else {
                //获取4位随机数
                billNum = (int) (Math.random() * 10000);
            }
        } catch (PoolException e) {
            log.error("获取服务单号流水时报错", e);
            //获取4位随机数
            billNum = (int) (Math.random() * 10000);
            Constants.REDIS_IS_USABLE = false;
        }
        //单号中流水不足4位的，则前面补0，如：123，变成0123
        if (billNum < 1000) {
            return noDate + String.format("%04d", billNum);
        }
        return noDate + billNum;
    }
    /**
     * 获取注册用户ID
     * @param stringRedisTemplate redis对象
     * @param project 哪个项目,0:同城服务，1：全民吐槽
     * @return
     */
    public static String getRegisterUserId(StringRedisTemplate stringRedisTemplate,int project) {
        long redisUserId = 0;
        try {
            if (Constants.REDIS_IS_USABLE) {
                //从redis中获取单号的流水
                redisUserId = stringRedisTemplate.opsForValue().increment(getIncrementKey(project));
            } else {
                redisUserId = -1;
                idIncrAdd(project);
            }
        } catch (PoolException e) {
            log.error("获取注册用户ID时报错", e);
            Constants.REDIS_IS_USABLE = false;
        }
        if (redisUserId == -1){
            return null;
        }
        //单号中流水不足9位的，则前面补0，如：123，变成000000123
        if (redisUserId < 100000000) {
            return String.format("%09d", redisUserId);
        }
        return String.valueOf(redisUserId);
    }

    /**
     * 获取项目注册用户ID的redis自增数
     * @param project 项目
     * @return 项目对应的自增key
     */
    private static String getIncrementKey(int project){
        if (project == 0){
            return Constants.SCS_REG_U_ID_INCR;
        }
        return Constants.VENT_REG_U_ID_INCR;
    }
    /**
     * 用户ID的自增
     * @param project 项目
     * @return 项目对应的自增key
     */
    private static void idIncrAdd(int project){
        if (project == 0){
            Constants.SCS_U_ID_INCR_ADD ++;
            return;
        }
        Constants.VENT_U_ID_INCR_ADD ++;
    }
}
